<?php

$self_name = basename(__FILE__);
$options = getopt("L:hd", ['wget_num::', 'tries::', 'missing::']);
if (empty($options)) {
    echo 'Usage: php ' . $self_name . ' [OPTION]... [URL]...' . "\r\n\r\n";
    echo 'Try `php ' . $self_name . ' -h` for more options.' . "\r\n";
    exit(0);
}
if (isset($options['L']) && !empty($options['L'])) {
    list($mov_name, $url) = explode('$', $options['L'], 2);
    if (empty($url) && empty($mov_name)) {
        exit(0);
    } elseif (empty($url)) {
        $url = $mov_name;
        $mov_name = date('YmdHis') . substr(uniqid(), 7, 4);
    }
} else {
    $options['h'] = '';
}
if (isset($options['h'])) {
    printCmd('Example:');
    printCmd($self_name . ' -L SaveName$http://www.example.com/index.m3u8', 2);
    printCmd('-L URI 设定下载M3U8地址，必须存在。可以xxx$http://xxx，或者http://xxx');
    printCmd('--wget_num=NUMBER 指定启动wget的数量，模拟多线程，默认1个。');
    printCmd('--tries=NUMBER 当文件数量下载不完全时指定重试次数，默认3次。');
    printCmd('--missing=NUMBER 当缺失文件少于多少时可以合并文件，默认5个。');
    exit(0);
}
//启动wget数量，默认为1
if (isset($options['wget_num']) && !empty($options['wget_num'])) {
    define("WGET_NUM", @intval($options['wget_num']));
} else {
    define("WGET_NUM", 1);
}
//文件不完全时，重试次数
if (isset($options['tries']) && !empty($options['tries'])) {
    define("TRIES_NUMBER", @intval($options['tries']));
} else {
    define("TRIES_NUMBER", 3);
}
//文件缺少多少时，不允许合并
if (isset($options['missing']) && !empty($options['missing'])) {
    define("MISSING_FILE", @intval($options['missing']));
} else {
    define("MISSING_FILE", 5);
}
//调试显示更多数据
if (isset($options['d']) && !empty($options['d'])) {
    $debug = true;
} else {
    $debug = false;
}
//创建合并文件保存目录
if (!file_exists('_down/')) {
    mkdir('_down/', 0777, true);
}
$mov_save_path = str_replace('\\', '/', __DIR__) . '/_down/';
//合并文件保存完整路径
$movfullpath = $mov_save_path . $mov_name . '.mp4';
$md5 = md5($url);
//创建"$md5/res_"和"$md5/wget_"文件夹
if (!file_exists($md5 . '/res_/')) {
    mkdir($md5 . '/res_/', 0777, true);
}
if (!file_exists($md5 . '/wget_/')) {
    mkdir($md5 . '/wget_/', 0777, true);
}
$url_1 = substr($url, 0, strpos($url, '/', 8));
$url_2 = substr($url, 0, strrpos($url, '/') + 1);

$exec_result = exec('wget -q -O ' . $md5 . '/_index.m3u8 ' . $url);
$handler = fopen($md5 . '/_index.m3u8', "r");
if (!filesize($md5 . '/_index.m3u8')) {
    printCmd('获取M3U8文件不成功，请检查网络或者M3U8地址');
    exit(0);
}
$is_hls = false;
$key = '';
$path_second = '';
while (!feof($handler)) {
    //fgets逐行读取，4096最大长度，默认为1024
    $m = fgets($handler, 4096);
    //查找字符串
    if (substr_count($m, "#EXT-X-STREAM-INF:") > 0) {
        if ($debug) {
            //打印结果
            print_r($m);
        }
        $is_hls = true;
        continue;
    }
    if ($is_hls) {
        $path_second = $m;
        if ($debug) {
            print_r($path_second);
        }
        break;
    }
}
//关闭文件
fclose($handler);

$path_three = '';
if ($path_second) {
    if (strpos($path_second, '/') === 0) {
        $path_three = $url_1 . $path_second;
    } else {
        $path_three = $url_2 . $path_second;
    }
}
//print_r($url_1);
//print_r($url_2);
//print_r($path_three);

//二次解析的文件或url地址
if (!$path_three) {
    $path_three = $md5 . '/_index.m3u8';
} else {
    $exec_result = exec('wget -q -O ' . $md5 . '/_hls.m3u8 ' . $path_three);
    $path_three = $md5 . '/_hls.m3u8';
    if (!filesize($path_three)) {
        printCmd('获取二次解析文件不成功，请检查网络或者M3U8地址');
        exit(0);
    }
}
$content_ffmpeg = '';
$content_wget = [];
$path_four = '';
//保存key文件路径
$path_key = '';
$handler = fopen($path_three, "r");
while (!feof($handler)) {
    $m = fgets($handler, 4096);
    if (substr_count($m, "#EXTINF:") > 0) {
        $content_ffmpeg .= $m;
        $m = fgets($handler, 4096);
        if (stripos($m, 'https://') === 0 || stripos($m, 'http://') === 0) {
            $path_four = $m;
        } else {
            if (strpos($m, '/') === 0) {
                $path_four = $url_1 . $m;
            } else {
                $path_four = $url_2 . $m;
            }
        }
        $content_wget[] = $path_four;
        $content_ffmpeg .= str_replace('\\', '/', __DIR__) . '/' . $md5 . '/res_/' . substr($path_four, strrpos($path_four, '/') + 1);
    } elseif (false && preg_match("#\.ts|\.image|^https://|^http://#i", $m)) {
        if (stripos($m, 'https://') === 0 || stripos($m, 'http://') === 0) {
            $path_four = $m;
        } elseif (substr_count($m, ".ts") > 0 || substr_count($m, ".image") > 0) {
            if (strpos($m, '/') === 0) {
                $path_four = $url_1 . $m;
            } else {
                $path_four = $url_2 . $m;
            }
        }
        $content_wget[] = $path_four;
        $content_ffmpeg .= str_replace('\\', '/', __DIR__) . '/' . $md5 . '/res_/' . substr($path_four, strrpos($path_four, '/') + 1);
    } elseif (substr_count($m, "#EXT-X-KEY:") > 0) {
        if ($debug) {
            print_r($m);
        }
        preg_match('/,URI="(.+?)"/', $m, $matches);
        if (isset($matches[1])) {
            $path_key = $matches[1];
        }
        if ($path_key) {
            $content_ffmpeg .= preg_replace('/,URI="(.+?)"/', ',URI="' . str_replace('\\', '/', __DIR__) . '/' . $md5 . '/_key.key"', $m);
        }
    } elseif (substr_count($m, "#EXT-X-VERSION:") > 0) {
        $content_ffmpeg .= $m;
    } else {
        $content_ffmpeg .= $m;
    }
}
fclose($handler);

file_put_contents($md5 . '/_ffmpeg.m3u8', $content_ffmpeg) && $content_ffmpeg = null;
if ($debug) {
    file_put_contents($md5 . '/_wget.txt', implode("", $content_wget));
}
if ($path_key) {
    if (strpos($path_key, 'http://') !== 0 && strpos($path_key, 'https://') !== 0) {
        if (strpos($path_key, '/') === 0) {
            $path_key = $url_1 . $path_key;
        } else {
            $path_key = $url_2 . $path_key;
        }
    }
    $exec_result = exec('wget -q -O ' . $md5 . '/_key.key -t 0 -N --waitretry=1 --no-proxy -c ' . $path_key);
}
//每次启动$wget_num个wget
$wget_num = WGET_NUM;
//统计ts数量
$wget_total = $ts_total = count($content_wget);
$wget_page = 1;
$wget_txtnum = ceil($wget_total / $wget_num);
while ($wget_total > 0) {
    $wget_splice = array_splice($content_wget, 0, $wget_txtnum);
    file_put_contents($md5 . '/wget_/' . $wget_page . '.txt', implode('', $wget_splice));
    $wget_page++;
    $wget_total -= $wget_txtnum;
}
printCmd('正在下载 ' . $mov_name . '，需要下载' . $ts_total . '个文件，请耐心等待……');
//重试次数，当下载文件数量不完整时重试$tries_number次
$tries_number = TRIES_NUMBER;
do {
    if ($tries_number < TRIES_NUMBER) {
        printCmd('文件下载不完全，共有' . $ts_total . '个文件，还差' . ($ts_total - $count_file) . '个文件，正在第' . (TRIES_NUMBER - $tries_number) . '次重试下载');
    }
    if (WGET_NUM == 1) {
        $exec_result = exec('wget -q --directory-prefix=' . $md5 . '/res_ -t 0 -N --waitretry=1 --no-proxy -c -i ' . $md5 . '/wget_/1.txt');
    } else {
        $exec_result = exec('wget_bat.bat ' . $md5);
    }
    $count_file = countFileNumber($md5 . '/res_/');
    $tries_number--;
} while ($tries_number > 0 && $count_file < $ts_total);

//缺失文件数，当缺失文件数小于$missing_file时开始合并
$missing_file = MISSING_FILE;
if ($ts_total - $count_file <= $missing_file) {
    printCmd('正在合并文件，请稍等……');
    if (!$debug) {
        $exec_result = exec('ffmpeg -loglevel -8 -threads 0 -allowed_extensions ALL -protocol_whitelist "file,http,https,crypto,tcp" -i ' . $md5 . '/_ffmpeg.m3u8 -c copy -y -bsf:a aac_adtstoasc "' . $movfullpath . '"');
    } else {
        $exec_result = exec('ffmpeg -loglevel 56 -threads 0 -allowed_extensions ALL -protocol_whitelist "file,http,https,crypto,tcp" -i ' . $md5 . '/_ffmpeg.m3u8 -c copy -y -bsf:a aac_adtstoasc "' . $movfullpath . '"');
    }
    printCmd('合并完成，文件保存在 ' . str_replace('/', '\\', $movfullpath), 2);
    //删除第一行
    delFileLine('m3u8-list.txt');
    if (!$debug) {
        //合并完成后删除$md5文件夹
        exec(sprintf('rd /s /q %s', escapeshellarg($md5)));
    }
} else {
    printCmd('缺失文件数大于' . $missing_file . '，请检查网络并重新下载', 2);
}

function countFileNumber($path)
{
    $filenum = 0;

    try {
        if ($handle = opendir($path)) {
            //echo "Directory handle: $handle\n";
            //echo "Files:\n";
            while (false !== ($file = readdir($handle))) {
                if ($file != "." && $file != "..") {
                    //echo "$file\n";
                    $ext = strrchr($file, '.');
                    if ('.ts' == $ext || '.image' == $ext) {
                        $filenum++;
                    }
                }
            }
            closedir($handle);
        }
    } catch (Exception $e) {
    }
    return $filenum;
}

function printCmd($str, $line = 1)
{
    echo $str;
    //echo iconv('UTF-8', 'GB2312', $str);
    for ($i = 0; $i < $line; $i++) {
        echo "\r\n";
    }
}

function delFileLine($fileName, $lineNum = 1)
{
    $newfp  = '';
    //读取文件数据到数组中
    $farray = file($fileName);
    for ($i = 0; $i < count($farray); $i++) {
        //判断删除的行,strcmp是比较两个数大小的函数
        if (strcmp($i + 1, $lineNum) == 0) {
            continue;
        }
        //删除文件中的所有空行
        if (trim($farray[$i]) != '') {
            //重新整理后的数据
            $newfp .= $farray[$i];
        }
    }
    //以写的方式打开文件
    $fp = @fopen($fileName, 'w');
    @fputs($fp, $newfp);
    @fclose($fp);
}
